﻿using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options;

namespace CleanLite.DynamicApi
{
    /// <summary>
    /// 动态API约定配置
    /// </summary>
    /// <remarks>
    /// 继承自 <see cref="IApplicationModelConvention"/>
    /// <para>
    /// 自定义控制器、Swagger文档、参数绑定等相关配置
    /// </para>
    /// <para>
    /// 参考博客<a href="https://www.cnblogs.com/savorboard/p/dontnet-IApplicationModelConvention.html">IApplicationModelConvention</a>
    /// </para>
    /// </remarks>
    /// <param name="dynamicController"></param>
    internal class DynamicApiConvention(IOptions<DynamicApiOptions> options) : IApplicationModelConvention
    {
        private readonly DynamicApiOptions options = options.Value;

        /// <summary>
        /// 将约定应用于ApplicationModel
        /// </summary>
        /// <param name="application"></param>
        public void Apply(ApplicationModel application)
        {
            // 遍历应用程序下的所有控制器
            foreach (var controllerModel in application.Controllers)
            {
                //检查是否为动态API控制器,非动态API控制器则跳过(动态API继承了IDynamicApi)
                var type = controllerModel.ControllerType.AsType();

                //检查是否开启授权
                var hasAuthorize = controllerModel.Filters.Any(x => x is AllowAnonymousFilter);

                if (!hasAuthorize)
                {
                    controllerModel.Filters.Add(new AuthorizeFilter());
                }

                //走自定义控制器识别策略
                if (options.CustomControllerFeatureProvider.IsController(type))
                {
                    controllerModel.ControllerName = controllerModel.ControllerName.IgnoreSuffix(options.IgnoreControllerSuffix);
                    DynamicApiConfigure(controllerModel);
                }
            }
        }

        /// <summary>
        /// 配置动态API控制器
        /// </summary>
        /// <param name="controllerModel">控制器模型</param>
        private void DynamicApiConfigure(ControllerModel controllerModel)
        {
            DynamicParameters(controllerModel);
            DynamicActionRoute(controllerModel);
            DynamicSwaggerApiExplorer(controllerModel);
        }

        /// <summary>
        /// 配置Swagger文档分组以及是否显示(默认都显示)
        /// </summary>
        /// <param name="controllerModel"></param>
        private static void DynamicSwaggerApiExplorer(ControllerModel controllerModel)
        {
            if (controllerModel.ApiExplorer.GroupName == null)
            {
                controllerModel.ApiExplorer.GroupName = controllerModel.ControllerName;
            }

            if (controllerModel.ApiExplorer.IsVisible == null)
            {
                controllerModel.ApiExplorer.IsVisible = true;
            }
        }

        /// <summary>
        /// 配置动态Action路由
        /// </summary>
        /// <param name="controllerModel"></param>
        private void DynamicActionRoute(ControllerModel controllerModel)
        {
            foreach (var action in controllerModel.Actions)
            {
                AddActionRoute(controllerModel.ControllerName, action);
            }
        }

        /// <summary>
        /// 添加Action路由
        /// </summary>
        /// <param name="controllerName">控制器名称</param>
        /// <param name="action"></param>
        private void AddActionRoute(string controllerName, ActionModel action)
        {
            foreach (var selector in action.Selectors)
            {
                if (selector.AttributeRouteModel == null)
                {
                    CreateActionRouteModel(controllerName, action.ActionName);
                }
                else
                {
                    CreateActionRouteModel(controllerName, selector.AttributeRouteModel.Template!);
                }
                selector.AttributeRouteModel = selector.AttributeRouteModel == null
                  ? CreateActionRouteModel(controllerName, action.ActionName)
                  : CreateActionRouteModel(controllerName, selector.AttributeRouteModel.Template!);

                TryAddHttpActions(selector, action);
            }
        }

        /// <summary>
        /// 尝试为没有HTTP动作约束的选择器添加默认的HTTP动作约束
        /// </summary>
        /// <param name="selectorModel"></param>
        /// <param name="actionModel"></param>
        private static void TryAddHttpActions(SelectorModel selectorModel, ActionModel actionModel)
        {
            if (selectorModel.ActionConstraints.Any(x => x is HttpMethodActionConstraint))
            {
                return;
            }

            bool isFromBody = actionModel.Parameters.Any(x => x.BindingInfo?.BindingSource == BindingSource.Body);
            selectorModel.ActionConstraints.Add(new HttpMethodActionConstraint([isFromBody ? "POST" : "GET"]));
        }

        /// <summary>
        /// 生成路由
        /// <para>例如<c>/api/User/GetUser</c></para>
        /// </summary>
        /// <param name="controllerName"></param>
        /// <param name="actionName"></param>
        /// <returns></returns>
        private AttributeRouteModel CreateActionRouteModel(string controllerName, string actionName)
        {
            actionName = actionName.IgnoreSuffix(options.IgnoreMethodSuffix);
            var apiPreFix = options.ApiPrefix;
            var route = $"{apiPreFix}/{controllerName}/{actionName}".Replace("//", "/");
            return new AttributeRouteModel(new RouteAttribute(route));
        }

        /// <summary>
        /// 动态参数绑定配置
        /// </summary>
        /// <param name="controllerModel"></param>
        private static void DynamicParameters(ControllerModel controllerModel)
        {
            foreach (var action in controllerModel.Actions)
            {
                foreach (var para in action.Parameters)
                {
                    //如果参数已经有绑定特性则跳过,例如[FromQuery],[FromBody]等
                    if (para.BindingInfo != null)
                    {
                        continue;
                    }

                    if (!IsUseFromQuery(para.ParameterType))
                    {
                        if (!IsUseFromBody(action))
                        {
                            //throw new InvalidOperationException("复杂类型参数不能绑定到GET请求");
                        }
                        para.BindingInfo = BindingInfo.GetBindingInfo([new FromBodyAttribute()]);
                    }
                }
            }
        }

        /// <summary>
        /// 判断是否为简单类型（包括基础类型、字符串、枚举、日期等）以此选择是否使用FromQuery绑定
        /// </summary>
        ///<remarks>
        /// 基本类型包括：
        /// <list type="bullet">
        /// <item><description>Boolean</description></item>
        /// <item><description>Byte</description></item>
        /// <item><description>SByte</description></item>
        /// <item><description>Int16</description></item>
        /// <item><description>UInt16</description></item>
        /// <item><description>Int32</description></item>
        /// <item><description>UInt32</description></item>
        /// <item><description>Int64</description></item>
        /// <item><description>UInt64</description></item>
        /// <item><description>IntPtr</description></item>
        /// <item><description>UIntPtr</description></item>
        /// <item><description>Char</description></item>
        /// <item><description>Double</description></item>
        /// <item><description>Single</description></item>
        /// </list>
        /// </remarks>
        /// <param name="type"></param>
        /// <returns></returns>
        private static bool IsUseFromQuery(Type type)
        {
            //若是可空类型获取基础类型再进行判断,非空类型则为NULL
            var underlyingType = Nullable.GetUnderlyingType(type) ?? type;

            return underlyingType.IsPrimitive
                || underlyingType.IsEnum
                || underlyingType == typeof(string)
                || underlyingType == typeof(DateTime)
                || underlyingType == typeof(decimal)
                || underlyingType == typeof(Guid);
        }

        /// <summary>
        /// 判断是否可以绑定到FromBody
        /// </summary>
        /// <param name="actionModel"></param>
        /// <returns></returns>
        private static bool IsUseFromBody(ActionModel actionModel)
        {
            foreach (var selector in actionModel.Selectors)
            {
                foreach (var actionConstraint in selector.ActionConstraints)
                {
                    if (actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint)
                    {
                        if (httpMethodActionConstraint.HttpMethods.Contains("GET", StringComparer.OrdinalIgnoreCase))
                        {
                            return false;
                        }
                    }
                }
            }
            return true;
        }
    }
}